package cn.edu.lcu.cs.designpattern.flyweight.poker.iterable_deck;

import cn.edu.lcu.cs.designpattern.flyweight.poker.Rank;
import cn.edu.lcu.cs.designpattern.flyweight.poker.Suit;
import lombok.Getter;
import lombok.ToString;

import java.util.*;

/**
 * 一副牌.
 * 实现了Iterator接口，这是一个card类型的迭代器。
 * 实现了Iterable接口，客户程序就可以使用foreach语句与forEach()方法进行调用。
 *
 * @author ling
 * @date 2022/9/11 19:33
 */
@Getter
@ToString
public class Deck implements Iterator<Card>, Iterable<Card> {

    /**
     * 存储洗牌后尚未发出的牌.
     * 实例变量，因为每副牌的这个列表都是不一样的。
     */
    private List<Card> cardsToBeDealt = new LinkedList<>();

    /**
     * 享元对象池。
     * 无论有多少副牌，52种花色点数组合是不变的，实例变量，节省空间。
     * 用二维映射存储52张扑克牌，保证每张牌只有一个实例。
     * 也可以用二维数组存储52张牌。
     */
    private final static Card[][] cardsPool = new Card[Suit.values().length][Rank.values().length];

    // 静态初始化52张扑克牌的映射，保证每张牌只有一个实例。
    static {
        for (Suit suit : Suit.values()) {
            for (Rank rank : Rank.values()) {
                cardsPool[suit.ordinal()][rank.ordinal()] = new Card(suit, rank);
            }
        }
    }

    /**
     * 提供公开的接口，供外部调用，根据花色与点数返回唯一的扑克版实例。
     *
     * @param suit
     * @param rank
     * @return
     */
    public static Card get(Suit suit, Rank rank) {
        return cardsPool[suit.ordinal()][rank.ordinal()];
    }

    /**
     * 随机发一张牌.
     * 因为undealtCards已经是随机洗过的牌，所以发牌时不需再次洗牌。
     *
     * @return
     */
    public Card deal() {
        if (!cardsToBeDealt.isEmpty()) {
            // 从已经随机顺序的牌中取一张牌
            return cardsToBeDealt.remove(0);
        } else {
            //return null;
            throw new IllegalStateException("牌都发光了，重新洗牌吧。");
        }
    }

    /**
     * 将所有牌均分为 n 等分，分为n个牌堆（Card Pile）。
     *
     * @param pileNumber 牌堆数量
     * @return
     */
    public List<List<Card>> deal(int pileNumber) {
        List<List<Card>> cards = new LinkedList<>();
        for (int i = 0; i < pileNumber; i++) {
            cards.add(new ArrayList<>());
        }
        int size = cardsToBeDealt.size();
        for (int i = 0; i < size; i++) {
            Card card = deal();
            cards.get(i % pileNumber).add(card);
        }
        return cards;
    }

    /**
     * 洗牌
     */
    public void shuffle() {
        // 洗牌之前先清除原来的牌
        cardsToBeDealt.clear();
        // 把二维数组中所有牌转到一维列表中，方便抽牌。
        Arrays.stream(cardsPool)
                .map(cards -> Arrays.asList(cards))
                .forEach(cardsToBeDealt::addAll);
        // 调用工具类中的方法，把列表中的元素随机分配。
        Collections.shuffle(cardsToBeDealt);
    }

    @Override
    public boolean hasNext() {
        return !cardsToBeDealt.isEmpty();
    }

    @Override
    public Card next() {
        return cardsToBeDealt.remove(0);
    }

    /**
     * 这个方法源于Iterable接口。
     * 自身实现了Iterator接口，所以可以返回自身的引用。
     *
     * @return
     */
    @Override
    public Iterator<Card> iterator() {
        return this;
    }
}
